//
//  C45Learner.m
//  Pennyworth
//
//  Created by Chris Karr on 3/29/08.
//  Copyright 2008 Chris J. Karr. All rights reserved.
//

#import "C45Learner.h"
#import "LabelManager.h"
#import "Context.h"

NSInteger C45Sort (id one, id two, void * context)
{
	if (([one isEqual:@"true"] || [one isEqual:@"false"]) &&
		([two isEqual:@"true"] || [two isEqual:@"false"]))
		return [two compare:one];

	return [one compare:two];
}

@implementation C45Learner

@synthesize multipleValueList;

- (CGFloat) classInfoForExamples:(NSArray *) exampleArray attribute:(NSString *) attribute
{
	NSUInteger s = [exampleArray count];

	NSMutableDictionary * exampleCounts = [NSMutableDictionary dictionary];
	
	for (NSDictionary * example in exampleArray)
	{
		NSString * label = [example valueForKey:LABEL_KEY];

		NSNumber * count = [exampleCounts valueForKey:label];
		
		if (count == nil)
			count = [NSNumber numberWithInt:0];
			
		if (attribute != nil)
		{
			if ([example valueForKey:attribute] != nil)
				count = [NSNumber numberWithInt:([count integerValue] + 1)];
			else
				s = s - 1;
		}
		else
			count = [NSNumber numberWithInt:([count integerValue] + 1)];
		
		[exampleCounts setValue:count forKey:label];
	}
	
	// If all values are unknown...
	
	CGFloat sum = 0;
	
	//	for (NSString * outcome in [exampleCounts allKeys])
	for (NSNumber * value in [exampleCounts allValues])
	{
		CGFloat valueFloat = [value doubleValue];
		if (valueFloat != 0 && s != 0)
		{
			CGFloat f = valueFloat / ((CGFloat) s);
			sum += (f * log2 (f));
		}
	}

	return 0 - sum;
}

- (CGFloat) attributeInfoForExamples:(NSArray *) exampleArray attribute:(NSString *) attribute
{
	NSMutableDictionary * partition = [NSMutableDictionary dictionary];
	
	CGFloat count = 0;
	
	for (NSDictionary * example in exampleArray)
	{
		NSObject * value = [example valueForKey:attribute];
		
		if (value != nil)
		{
			NSMutableArray * attributeMatches = [partition objectForKey:value];
		
			if (attributeMatches == nil)
			{	
				attributeMatches = [NSMutableArray array];
				[partition setObject:attributeMatches forKey:value];
			}
		
			[attributeMatches addObject:example];
			
			count = count + 1;
		}
	}
	
	CGFloat sum = 0;
	
	for (NSMutableArray * matches in [partition allValues])
	{
		CGFloat weight = (((CGFloat) [matches count]) /  ((CGFloat) count));
		
		sum += weight * [self classInfoForExamples:matches attribute:attribute];
	}
	
	return sum;
}

- (CGFloat) gainForExamples:(NSArray *) exampleArray attribute:(NSString *) attribute
{
	// See Quinlan, p. 29
	
	CGFloat knowns = 0;
	
	for (NSDictionary * example in exampleArray)
	{
		if ([example valueForKey:attribute] != nil)
			knowns = knowns + 1;
	}
	
	CGFloat classInfo = [self classInfoForExamples:exampleArray attribute:attribute];
	CGFloat attInfo = [self attributeInfoForExamples:exampleArray attribute:attribute];
	
	CGFloat gain = (knowns  / ((CGFloat) [exampleArray count])) * (classInfo  - attInfo);
	
	return gain;
}

- (CGFloat) gainRatioForExamples:(NSArray *) exampleArray attribute:(NSString *) attribute
{
	NSMutableDictionary * partition = [NSMutableDictionary dictionary];
	
	for (NSDictionary * example in exampleArray)
	{
		NSObject * value = [example valueForKey:attribute];
		
		if (value == nil)
			value = UNKNOWN_VALUE;
			
		NSMutableArray * attributeMatches = [partition objectForKey:value];
		
		if (attributeMatches == nil)
		{	
			attributeMatches = [NSMutableArray array];
			[partition setObject:attributeMatches forKey:value];
		}
		
		[attributeMatches addObject:example];
	}

	CGFloat splitInfo = 0;
	
	for (NSArray * matches in [partition allValues])
	{
		CGFloat exampleRatio = (((CGFloat) [matches count]) / ((CGFloat) [exampleArray count]));

		splitInfo -=  exampleRatio * log2 (exampleRatio);
	}

	CGFloat gain = [self gainForExamples:exampleArray attribute:attribute];

	if (splitInfo == 0)
		return 0;

	CGFloat gainRatio = gain / splitInfo;
		
	return gainRatio;
}

- (NSDictionary *) bestFeatureForExamples:(NSArray *) exampleArray
{
	NSMutableSet * exampleAttributes = [NSMutableSet set];
	
	for (NSDictionary * example in exampleArray)
		[exampleAttributes addObjectsFromArray:[example allKeys]];

	[exampleAttributes removeObject:LABEL_KEY];

	NSDictionary * bestFeature = nil;
	CGFloat bestGain = 0;

	for (NSString * attribute in exampleAttributes)
	{
		CGFloat gain = bestGain;
		
		NSMutableDictionary * thisFeature = [NSMutableDictionary dictionaryWithObject:attribute forKey:FEATURE_NAME];
		
		if ([attribute rangeOfString:NUMERIC].location == NSNotFound)
			gain = [self gainRatioForExamples:exampleArray attribute:attribute];
		else
		{
			// Handle continous values
			
			CGFloat attBestGain = 0.0;
			NSNumber * split = nil;

			// NSMutableArray * examplesCopy = [[NSUnarchiver unarchiveObjectWithData:[NSArchiver archivedDataWithRootObject:exampleArray]] mutableCopy];

			NSMutableArray * examplesCopy = [NSMutableArray array];
			
			for (NSDictionary * example in exampleArray)
			{
				if ([example count] > 0)
					[examplesCopy addObject:[NSMutableDictionary dictionaryWithDictionary:example]];
			}

			NSSortDescriptor * sort = [[NSSortDescriptor alloc] initWithKey:attribute ascending:YES];

			[examplesCopy sortUsingDescriptors:[NSArray arrayWithObject:sort]];

			NSMutableSet * values = [NSMutableSet set];
			
			for (NSMutableDictionary * example in examplesCopy)
			{
				NSNumber * value = [example valueForKey:attribute];
				
				if (value != nil)
					[values addObject:value];
			}
			
			for (NSNumber * value in [[values allObjects] sortedArrayUsingSelector:@selector(compare:)])
			{
				NSString * tempAttribute = [NSString stringWithFormat:@"%@ >= %f %@", attribute, [value doubleValue], NUMERIC];

				for (NSMutableDictionary * example in examplesCopy)
				{
					NSNumber * exampleValue = [example valueForKey:attribute];
					
					if (exampleValue != nil)
					{
						if ([exampleValue doubleValue] >= [value doubleValue])
							[example setValue:@"true" forKey:tempAttribute];
						else
							[example setValue:@"false" forKey:tempAttribute];
					}
				}
				
				CGFloat valueGain = [self gainRatioForExamples:examplesCopy attribute:tempAttribute];
				
				if (valueGain >= attBestGain)
				{
					attBestGain = valueGain;
					split = value;
				}
			}
			
			// NSLog (@"for attribute %@, split at %@ (gain = %f)", attribute, split, attBestGain);
			
			gain = attBestGain;
			// attribute = [NSString stringWithFormat:@"%@ <= %@ %@", attribute, split, NUMERIC, nil]; 
			
			[thisFeature setValue:split forKey:SPLIT_VALUE];
		}
		
		if (gain > bestGain)
		{
			bestGain = gain;
			bestFeature = thisFeature;
		}
	}
	
	return bestFeature;
}

- (NSMutableDictionary *) treeForExamples:(NSArray *) exampleArray
{
	NSMutableDictionary * node = [NSMutableDictionary dictionary];
	
	NSMutableDictionary * outcomes = [NSMutableDictionary dictionary];
	
	// Test to see if all outcomes are the same...
	
	NSString * last = nil;
	BOOL match = YES;
	
	for (NSDictionary * example in exampleArray)
	{
		NSString * outcome = [example valueForKey:LABEL_KEY];
		
		if (last == nil)
			last = outcome;
		else if (![last isEqual:outcome])
			match = NO;

		NSNumber * count = [outcomes valueForKey:outcome];
		
		if (count == nil)
			count = [NSNumber numberWithInteger:0];
			
		[outcomes setValue:[NSNumber numberWithInteger:(1 + [count intValue])] forKey:outcome];
	}
	
	if (match)
	{
		[node setValue:last forKey:LABEL_KEY];
		[node setValue:last forKey:MAX_OUTCOME];
		[node setValue:[NSNumber numberWithInteger:[exampleArray count]] forKey:EXAMPLE_COUNT];
		[node setValue:[NSNumber numberWithInteger:0] forKey:EXAMPLES_WRONG];
		[node setValue:[NSNumber numberWithInteger:1] forKey:OUTCOME_COUNT];
	}
	else
	{
		NSDictionary * feature = [self bestFeatureForExamples:exampleArray];

		// Calculate the maximum outcome
		
		NSInteger max = 0;
		NSInteger sum = 0;
		NSString * maxOutcome = nil;
		
		for (NSString * outcome in [outcomes allKeys])
		{
			NSInteger count = [[outcomes valueForKey:outcome] integerValue];
			
			sum += count;
			
			if (count >= max)
			{
				maxOutcome = outcome;
				max = count;
			}
		}

		[node setValue:maxOutcome forKey:MAX_OUTCOME];
		[node setValue:[NSNumber numberWithInteger:[exampleArray count]] forKey:EXAMPLE_COUNT];
		[node setValue:[NSNumber numberWithInteger:(sum - max)] forKey:EXAMPLES_WRONG];
		[node setValue:[NSNumber numberWithInteger:[[outcomes allKeys] count]] forKey:OUTCOME_COUNT];
		
		if (feature == nil)
		{
			// Unknown feature

			[node setValue:maxOutcome forKey:LABEL_KEY];
		}
		else
		{
			NSString * featureName = [feature valueForKey:FEATURE_NAME];
			NSNumber * split = [feature valueForKey:SPLIT_VALUE];

			// Outcomes differ - build a tree...

			NSMutableDictionary * matchingExamples = [NSMutableDictionary dictionary];
		
			[node setValue:feature forKey:FEATURE_SENSOR];

			NSMutableArray * unknowns = [NSMutableArray array];

			for (NSMutableDictionary * example in exampleArray)
			{
				NSMutableDictionary * thisExample = [NSMutableDictionary dictionaryWithDictionary:[example copy]];
			
				NSString * value = nil;

				if (split == nil)
				{
					value = [[thisExample valueForKey:featureName] description];
					[thisExample removeObjectForKey:featureName];
				}
				else
				{
					NSNumber * number = [thisExample valueForKey:featureName];
					
					if ([number doubleValue] >= [split doubleValue])
						value = @"true";
					else
						value = @"false";
				}
				
				if (value == nil)
					[unknowns addObject:thisExample];
				else
				{
					NSMutableArray * matches = [matchingExamples valueForKey:value];
			
					if (matches == nil)
					{
						matches = [NSMutableArray array];
						[matchingExamples setValue:matches forKey:value];
					}
				
					[matches addObject:thisExample];
				}
			}
			
			for (NSString * exampleKey in [matchingExamples allKeys])
			{
				NSMutableArray * matches = [matchingExamples valueForKey:exampleKey];
				
				NSUInteger matchCount = [matches count];
				
				[matches addObjectsFromArray:unknowns];
				
				NSMutableDictionary * child = [self treeForExamples:matches];
				[child setValue:[NSNumber numberWithInteger:matchCount] forKey:EXAMPLE_COUNT];
				
				[node setValue:child forKey:exampleKey];
			}

			[node setValue:[NSNumber numberWithInteger:[exampleArray count]] forKey:EXAMPLE_COUNT];
		}
	}
	
	return node;
}

- (CGFloat) prune:(NSMutableDictionary *) tree
{
	NSMutableArray * allKeys = [NSMutableArray arrayWithArray:[tree allKeys]];
	
	NSString * label = [tree valueForKey:LABEL_KEY];

	CGFloat wrong = [[tree valueForKey:EXAMPLES_WRONG] floatValue];
	CGFloat all = [[tree valueForKey:EXAMPLE_COUNT] floatValue];
	CGFloat count = [[tree valueForKey:OUTCOME_COUNT] floatValue];
	
	CGFloat myError = (wrong + (count - 1)) / (all + count);
	
	if (label != nil)
		return myError;
	
	[allKeys removeObject:FEATURE_SENSOR];
	[allKeys removeObject:EXAMPLE_COUNT];
	[allKeys removeObject:EXAMPLES_WRONG];
	[allKeys removeObject:OUTCOME_COUNT];
	[allKeys removeObject:MAX_OUTCOME];
	
	CGFloat nodesError = 0.0;
	
	for (NSString * nodeKey in allKeys)
	{
		NSMutableDictionary * node = [tree valueForKey:nodeKey];
		
		CGFloat nodeCount = [[node valueForKey:EXAMPLE_COUNT] floatValue];
		
		CGFloat ratio = nodeCount / all;
		nodesError += ratio * [self prune:node];
	}
	
	if (myError < nodesError)
	{
		// prune
		
		for (NSString * nodeKey in allKeys)
			[tree removeObjectForKey:nodeKey];

		[tree setValue:[tree valueForKey:MAX_OUTCOME] forKey:LABEL_KEY];
		
		return myError;
	}

	return nodesError;
}

- (void) addExample:(NSArray *) features forClass:(NSString *) label
{
	if (self.multipleValueList == nil)
		self.multipleValueList = [NSMutableDictionary dictionary];
		
	NSMutableDictionary * example = [NSMutableDictionary dictionaryWithObject:label forKey:LABEL_KEY];
	
	for (NSDictionary * feature in features)
	{
		NSObject * value = [feature valueForKey:FEATURE_OBSERVATION];
		NSString * name = [feature valueForKey:FEATURE_SENSOR];
	
		if ([value isKindOfClass:[NSArray class]])
		{
			NSMutableSet * multipleValues = [self.multipleValueList valueForKey:name];
			
			if (multipleValues == nil)
			{
				multipleValues = [NSMutableSet set];
				[self.multipleValueList setValue:multipleValues forKey:name];
			}
			
			NSArray * valueArray = (NSArray *) value;
			
			for (NSObject * item in valueArray)
			{
				NSString * newName = [NSString stringWithFormat:@"%@ :: %@", name, item, nil];
				[example setValue:@"true" forKey:newName];
				
				[multipleValues addObject:newName];
			}
		}
		else if ([value isKindOfClass:[NSNumber class]])
		{
			NSString * newName = [NSString stringWithFormat:@"%@ %@", name, NUMERIC];
			[example setValue:value forKey:newName];
		}
		else
			[example setValue:value forKey:name];
	}

	[self.examples addObject:example];
	
	for (NSSet * values in [self.multipleValueList allValues])
	{
		for (NSString * name in values)
		{
			for (NSDictionary * example in self.examples)
			{
				if ([example valueForKey:name] == nil)
					[example setValue:@"false" forKey:name];
			}
		}
	}

	[self save];
	[self load];
}

- (NSString *) labelForTree:(NSDictionary *) treeDict features:(NSArray *) features
{
	NSString * name = [[treeDict valueForKey:FEATURE_SENSOR] valueForKey:FEATURE_NAME];
	NSNumber * split = [[treeDict valueForKey:FEATURE_SENSOR] valueForKey:SPLIT_VALUE];

	NSString * label = [treeDict valueForKey:LABEL_KEY];
	
	if (label != nil)
		return label;

	// Swap out soon.
	if (name == nil)
		return nil;
	
	NSString * value = @"false";

	// NSLog (@"[%@] name = %@; split = %@", key, name, split);
	
	for (NSDictionary * feature in features)
	{
		NSObject * observation = [feature valueForKey:FEATURE_OBSERVATION];
		NSString * sensor = [feature valueForKey:FEATURE_SENSOR];
		
		if ([observation isKindOfClass:[NSArray class]])
		{
			NSArray * obs = (NSArray *) observation;
			
			for (NSObject * ob in obs)
			{
				NSString * newName = [NSString stringWithFormat:@"%@ :: %@", sensor, ob, nil];
			
				if ([name isEqualToString:newName])
					value = @"true";
			}
		}
		else if (split != nil && [name isEqualToString:[NSString stringWithFormat:@"%@ %@", sensor, NUMERIC]])
		{
			// NSLog (@"obs class = %@", [observation class]);

			if ([observation isKindOfClass:[NSNumber class]])
			{
				NSNumber * number = (NSNumber *) observation;
				
				// NSLog (@"%@ <= %@?", number, split);
				
				if ([number doubleValue] >= [split doubleValue])
					value = @"true";
			}
		}
		else if ([name isEqualToString:sensor])
		{
			value = [observation description];
		}
	}

	NSDictionary * child = [treeDict valueForKey:value];
		
	if (child != nil)
		return [self labelForTree:child features:features];
	
	NSMutableDictionary * predictions = [NSMutableDictionary dictionary];
	
	for (child in [treeDict allValues])
	{
		if ([child isKindOfClass:[NSDictionary class]])
		{
			NSString * prediction = [self labelForTree:child features:features];
			NSNumber * count = [child valueForKey:EXAMPLE_COUNT];

			if (prediction != nil)
			{
				NSNumber * predictionCount = [predictions valueForKey:prediction];
				
				if (predictionCount == nil)
					predictionCount = [NSNumber numberWithInteger:0];
					
				predictionCount = [NSNumber numberWithInteger:([predictionCount integerValue] + [count integerValue])];
				[predictions setValue:predictionCount forKey:prediction];
			}
		}
	}

	NSInteger max = 0;
	NSString * maxPrediction = nil;
	
	for (NSString * prediction in [predictions allKeys])
	{
		NSInteger count = [[predictions valueForKey:prediction] integerValue];
		
		if (count > max)
		{
			max = count;
			maxPrediction = prediction;
		}
	}
	
	return maxPrediction;
}

- (NSUInteger) nodeCountForTree:(NSDictionary *) tree
{
	if ([tree valueForKey:LABEL_KEY] != nil)
		return 1;

	NSUInteger count = 1;
	
	for (NSObject * item in [tree allValues])
	{
		if ([item isKindOfClass:[NSDictionary class]])
			count += [self nodeCountForTree:((NSDictionary *) item)];
	}
	
	return count;
}

- (NSUInteger) nodeDepthForTree:(NSDictionary *) tree
{
	if ([tree valueForKey:LABEL_KEY] != nil)
		return 1;

	NSUInteger maxDepth = 0;
	
	for (NSObject * item in [tree allValues])
	{
		if ([item isKindOfClass:[NSDictionary class]])
		{
			NSUInteger depth = [self nodeDepthForTree:((NSDictionary *) item)];
	
			if (depth > maxDepth)
				maxDepth = depth;
		}
	}
	
	return 1 + maxDepth;
}

- (NSString *) htmlForTree:(NSDictionary *) tree name:(NSString *) name
{
	NSString * label = [tree valueForKey:LABEL_KEY];
	if (label != nil)
	{
		if (name == nil)
		{
			NSString * html = [NSString stringWithFormat:@"<div class=\"node\">\
															<p class=\"value\">Default Value</p>\
															<p class=\"prediction\">%@</p>\
															<p class=\"data\">%@ matches</p>\
														  </div>", label, [tree valueForKey:EXAMPLE_COUNT], nil];
			return html;
		}
		else
		{
			NSString * html = [NSString stringWithFormat:@"<p class=\"value\"><a onMouseOver=\"this.style.cursor='pointer'\" onMouseOut=\"this.style.cursor=''\" onClick=\"toggleElement (this.parentNode.parentNode, '%@', true, false)\">%@</a></p>\
														   <p class=\"prediction\">%@</p>\
														   <p class=\"data\">%@ matches</p>", 
														 name, name, label, [tree valueForKey:EXAMPLE_COUNT], nil];
			return html;
		}
	}

	NSDictionary * feature = [tree valueForKey:FEATURE_SENSOR];
	NSString * featureDesc = [feature valueForKey:FEATURE_NAME];
	NSNumber * split = [feature valueForKey:SPLIT_VALUE];
	
	if (split != nil)
		featureDesc = [NSString stringWithFormat:@"%@ >= %@", featureDesc, split];
	
	NSMutableString * html = [NSMutableString stringWithFormat:@"<div class=\"node\">\
													<p class=\"sensor\">%@</p>", 
												featureDesc , nil];
												 
	for (NSString * k in [[tree allKeys] sortedArrayUsingFunction:C45Sort context:NULL])
	{
		NSObject * value = [tree valueForKey:k];
		
		if ([value isKindOfClass:[NSDictionary class]] && ![k isEqualToString:FEATURE_SENSOR])
		{
			[html appendString:@"<div class=\"node\">"];

			if ([value valueForKey:LABEL_KEY] == nil)
				[html appendFormat:@"<p class=\"value\"><a onMouseOver=\"this.style.cursor='pointer'\" onMouseOut=\"this.style.cursor=''\" onClick=\"toggleElement (this.parentNode.parentNode, '%@', true, false)\">%@</a></p>", k, k, nil];

			[html appendString:[self htmlForTree:((NSDictionary *) value) name:k]];
			
			[html appendString:@"</div>"];
		}
	}

	[html appendString:@"</div>"];
	
	return html;
}

- (NSString *) htmlRepresentation
{
	NSMutableString * htmlString = [NSMutableString stringWithContentsOfFile:
									[[NSBundle mainBundle] pathForResource:@"tree" ofType:@"html"]];
									
	[htmlString replaceOccurrencesOfString:@"LEARNER" withString:key options:NSLiteralSearch 
							range:NSMakeRange (0, [htmlString length])];

	NSString * exampleCount = [NSString stringWithFormat:@"%d", [self.examples count], nil];

	[htmlString replaceOccurrencesOfString:@"EXAMPLE_COUNT" withString:exampleCount options:NSLiteralSearch 
							range:NSMakeRange (0, [htmlString length])];

	NSString * nodeCount = [NSString stringWithFormat:@"%d", [self nodeCountForTree:self.decisionTree], nil];

	[htmlString replaceOccurrencesOfString:@"NODE_COUNT" withString:nodeCount options:NSLiteralSearch 
							range:NSMakeRange (0, [htmlString length])];

	NSString * nodeDepth = [NSString stringWithFormat:@"%d", [self nodeDepthForTree:self.decisionTree], nil];

	[htmlString replaceOccurrencesOfString:@"NODE_DEPTH" withString:nodeDepth options:NSLiteralSearch 
							range:NSMakeRange (0, [htmlString length])];

	NSString * nodeHtml = [self htmlForTree:self.decisionTree name:nil];

	[htmlString replaceOccurrencesOfString:@"NODE_HTML" withString:nodeHtml options:NSLiteralSearch 
							range:NSMakeRange (0, [htmlString length])];
							
	return htmlString;
}

- (C45Learner *) init
{
	if (self = [super init])
		[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(manageLabels:) name:LABEL_OPERATION object:nil];
	
	return self;
}

- (void) manageLabels:(NSNotification *) note
{
	NSDictionary * op = [note userInfo];
	
	if ([[op valueForKey:LABEL_TYPE] isEqual:key])
	{
		NSString * original = [op valueForKey:ORIGINAL_LABEL];
		
		NSMutableArray * matches = [NSMutableArray array];
		
		for (NSDictionary * example in self.examples)
		{
			if ([[example valueForKey:LABEL_KEY] isEqual:original])
				[matches addObject:example];
		}

		[self.examples removeObjectsInArray:matches];
		
		NSString * operation = [op valueForKey:OPERATION];
		if ([operation isEqual:RENAME_OPERATION])
		{
			NSString * newLabel = [op valueForKey:NEW_LABEL];

			for (NSDictionary * example in matches)
			{
				NSMutableDictionary * newExample = [NSMutableDictionary dictionaryWithDictionary:example];
				[newExample setValue:newLabel forKey:LABEL_KEY];
				
				[self.examples addObject:newExample];
			}
		}
	}

	[self save];
	[self load];
	
	[[NSNotificationCenter defaultCenter] postNotificationName:OBSERVATION_UPDATE object:self userInfo:nil];
}

/* - (void) awakeFromNib
{
	NSMutableArray * testExamples = [NSMutableArray array];
	
	[testExamples addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:
								@"sunny", @"Outlook",
								@"mild", @"Temp",
								@"low", @"Hum", 
								@"true", @"Wind",
								@"Play", LABEL_KEY, nil]];

	[testExamples addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:
								@"sunny", @"Outlook",
								@"hot", @"Temp",
								@"high", @"Hum", 
								@"true", @"Wind",
								@"Don't Play", LABEL_KEY, nil]];
								
	[testExamples addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:
								@"sunny", @"Outlook",
								@"hot", @"Temp",
								@"high", @"Hum", 
								@"false", @"Wind",
								@"Don't Play", LABEL_KEY, nil]];
								
	[testExamples addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:
								@"sunny", @"Outlook",
								@"mild", @"Temp",
								@"high", @"Hum", 
								@"false", @"Wind",
								@"Don't Play", LABEL_KEY, nil]];
								
	[testExamples addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:
								@"sunny", @"Outlook",
								@"cool", @"Temp",
								@"low", @"Hum", 
								@"false", @"Wind",
								@"Play", LABEL_KEY, nil]];
	
								//..........
															
	[testExamples addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:
								// @"overcast", @"Outlook",
								@"mild", @"Temp",
								@"high", @"Hum", 
								@"true", @"Wind",
								@"Play", LABEL_KEY, nil]];
								
	[testExamples addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:
								@"overcast", @"Outlook",
								@"hot", @"Temp",
								@"high", @"Hum", 
								@"false", @"Wind",
								@"Play", LABEL_KEY, nil]];
								
	[testExamples addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:
								@"overcast", @"Outlook",
								@"cool", @"Temp",
								@"low", @"Hum", 
								@"true", @"Wind",
								@"Play", LABEL_KEY, nil]];
								
	[testExamples addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:
								@"overcast", @"Outlook",
								@"hot", @"Temp",
								@"low", @"Hum", 
								@"false", @"Wind",
								@"Play", LABEL_KEY, nil]];
								
	[testExamples addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:
								@"rain", @"Outlook",
								@"mild", @"Temp",
								@"high", @"Hum", 
								@"true", @"Wind",
								@"Don't Play", LABEL_KEY, nil]];
								
	[testExamples addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:
								@"rain", @"Outlook",
								@"cool", @"Temp",
								@"low", @"Hum", 
								@"true", @"Wind",
								@"Don't Play", LABEL_KEY, nil]];
								
	[testExamples addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:
								@"rain", @"Outlook",
								@"mild", @"Temp",
								@"high", @"Hum", 
								@"false", @"Wind",
								@"Play", LABEL_KEY, nil]];
								
	[testExamples addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:
								@"rain", @"Outlook",
								@"cool", @"Temp",
								@"high", @"Hum", 
								@"false", @"Wind",
								@"Play", LABEL_KEY, nil]];
								
	[testExamples addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:
								@"rain", @"Outlook",
								@"mild", @"Temp",
								@"high", @"Hum", 
								@"false", @"Wind",
								@"Play", LABEL_KEY, nil]];

	NSDictionary * tree = [self treeForExamples:testExamples];

	NSLog (@"tree = %@", tree);
	
	NSArray * test = [NSArray array];
	
	NSLog (@"test (%@) = %@", test, [self labelForTree:tree features:test]);
	
	test = [NSArray arrayWithObject:[NSDictionary dictionaryWithObjectsAndKeys: @"Outlook", FEATURE_SENSOR, 
																				@"rain", FEATURE_OBSERVATION, nil]];

	NSLog (@"test (%@) = %@", test, [self labelForTree:tree features:test]);

	test = [NSArray arrayWithObjects:[NSDictionary dictionaryWithObjectsAndKeys: @"Outlook", FEATURE_SENSOR,
																				 @"rain", FEATURE_OBSERVATION, nil],
 									 [NSDictionary dictionaryWithObjectsAndKeys: @"Hum", FEATURE_SENSOR, 
																				@"low", FEATURE_OBSERVATION, nil], nil];

	NSLog (@"test (%@) = %@", test, [self labelForTree:tree features:test]);

	test = [NSArray arrayWithObjects:[NSDictionary dictionaryWithObjectsAndKeys: @"Outlook", FEATURE_SENSOR,
																				 @"rain", FEATURE_OBSERVATION, nil],
 									 [NSDictionary dictionaryWithObjectsAndKeys: @"Wind", FEATURE_SENSOR, 
																				@"true", FEATURE_OBSERVATION, nil], nil];

	NSLog (@"test (%@) = %@", test, [self labelForTree:tree features:test]);
   
   NSLog (@"info(T) = %f", [self gainRatioForExamples:testExamples attribute:@"Outlook"]);
   
}

- (C45Learner *) init
{
	if (self = [super init])
		[[NSNotificationCenter defaultCenter] removeObserver:self];
		
	return self;
} */

@end
